﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace Elentok.Common.Async
{
  #region Interfaces

  /// <summary>
  /// Defines an asynchronous task.
  /// <para>
  /// The <see cref="INotifyPropertyChanged.PropertyChanged"/> event is always raised in the thread 
  /// that called the <see cref="Start"/> method.
  /// </para>
  /// </summary>
  public interface IAsyncTask: INotifyPropertyChanged
  {
    /// <summary>
    /// Gets the current progress of the task (can be null, or a value between 0 or 100, including).
    /// </summary>
    /// <value>The progress.</value>
    int? Progress { get; }

    /// <summary>
    /// Gets the message to display.
    /// </summary>
    string Message { get; }

    /// <summary>
    /// Gets the data object (used to transfer data from the 
    /// background thread to the UI thread).
    /// </summary>
    object Data { get; }

    /// <summary>
    /// Gets the exception that was thrown in the background (if one was thrown, otherwise returns null).
    /// </summary>
    /// <value>The exception.</value>
    Exception Exception { get; }

    /// <summary>
    /// Gets the state of the action
    /// </summary>
    AsyncTaskState State { get; }

    /// <summary>
    /// Starts this task in a background thread.
    /// </summary>
    /// <exception cref="AsyncTaskAlreadyRunningException" />
    void Start();

    /// <summary>
    /// Cancels the task.
    /// </summary>
    void Cancel();
  }

  /// <summary>
  /// Defines an object that allows notifying when progress was changed.
  /// </summary>
  public interface IAsyncProgressNotifier
  {
    /// <summary>
    /// Notifies that the progress has changed.
    /// </summary>
    /// <param name="progress">
    /// The current progress (set to <c>null</c> if the progress is unknown).
    /// </param>
    /// <param name="message">The message.</param>
    /// <param name="data">
    /// The data (can be used to transfer information from 
    /// the background thread to the UI thread).
    /// </param>
    void NotifyProgress(int? progress, string message, object data);

    /// <summary>
    /// Notifies that the progress has changed.
    /// </summary>
    /// <param name="progress">
    /// The current progress (set to <c>null</c> if the progress is unknown).
    /// </param>
    /// <param name="message">The message.</param>
    void NotifyProgress(int? progress, string message);

    /// <summary>
    /// Gets a value indicating whether this task is pending cancellation.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if this task is pending cancellation; otherwise, <c>false</c>.
    /// </value>
    bool IsPendingCancellation { get; }
  }

  #endregion

  #region Enums & String-Classes

  /// <summary>
  /// Contains the names of the properties in the <see cref="IAsyncTask"/> interface
  /// (for use with the PropertyChanged event).
  /// </summary>
  public static class AsyncTaskProperties
  {
    /// <summary>
    /// The name of the <see cref="IAsyncTask.Progress"/> Property
    /// </summary>
    public const string Progress = "Progress";
    
    /// <summary>
    /// The name of the <see cref="IAsyncTask.Message"/> Property
    /// </summary>
    public const string Message = "Message";
    
    /// <summary>
    /// The name of the <see cref="IAsyncTask.Data"/> Property
    /// </summary>
    public const string Data = "Data";
    
    /// <summary>
    /// The name of the <see cref="IAsyncTask.Exception"/> Property
    /// </summary>
    public const string Exception = "Exception";

    /// <summary>
    /// The name of the <see cref="IAsyncTask.State"/> Property
    /// </summary>
    public const string State = "State";
  }

  /// <summary>
  /// Describes different states of an asynchronous action
  /// </summary>
  public enum AsyncTaskState
  {
    /// <summary>
    /// The action has not yet started
    /// </summary>
    Idle,

    /// <summary>
    /// The action is currently running
    /// </summary>
    Running,

    /// <summary>
    /// The user has asked to cancel the action
    /// (but it has not yet stopped)
    /// </summary>
    PendingCancellation,

    /// <summary>
    /// The action was cancelled and is no longer running
    /// </summary>
    Canceled,

    /// <summary>
    /// The action has completed and is no longer running
    /// </summary>
    Complete,

    /// <summary>
    /// An exception was thrown during the execution of the action
    /// </summary>
    ExceptionThrown
  }

  #endregion

  #region Exceptions

  /// <summary>
  /// Thrown when trying to start an <see cref="IAsyncTask"/> that is already running.
  /// </summary>
  [Serializable]
  public class AsyncTaskAlreadyRunningException : ApplicationException
  {
    /// <summary>
    /// Initializes a new instance of the <see cref="AsyncTaskAlreadyRunningException"/> class.
    /// </summary>
    public AsyncTaskAlreadyRunningException() { }
    
    /// <summary>
    /// Initializes a new instance of the <see cref="AsyncTaskAlreadyRunningException"/> class.
    /// </summary>
    /// <param name="message">The message.</param>
    public AsyncTaskAlreadyRunningException(string message) : base(message) { }
    
    /// <summary>
    /// Initializes a new instance of the <see cref="AsyncTaskAlreadyRunningException"/> class.
    /// </summary>
    /// <param name="message">The message.</param>
    /// <param name="inner">The inner.</param>
    public AsyncTaskAlreadyRunningException(string message, Exception inner) : base(message, inner) { }
    
    /// <summary>
    /// Initializes a new instance of the <see cref="AsyncTaskAlreadyRunningException"/> class.
    /// </summary>
    /// <param name="info">The object that holds the serialized object data.</param>
    /// <param name="context">The contextual information about the source or destination.</param>
    protected AsyncTaskAlreadyRunningException(
    System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context)
      : base(info, context) { }
  }

  #endregion
}
